
package hasthi.instrument;

import static hasthi.common.constants.HasthiConstants.HOST_AGENT_PORT;
import static hasthi.common.constants.HasthiConstants.Actions.COMMAND_TOMCAT_START;
import static hasthi.common.constants.HasthiConstants.Actions.COMMAND_TOMCAT_STOP;
import static hasthi.common.constants.HasthiConstants.Actions.COMMAND_PROCESS_KILL;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_CATEGORY;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_CONFIGURATION;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_DESCRIPTION;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_HEARTBEAT;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_MATRICS1;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_OPERATIONAL_STATUS;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_OPERATIONAL_STATUS2;
import static hasthi.common.constants.WSDMConstants.CAPABILITY_STATE;
import static hasthi.common.constants.WSDMConstants.MUWS2_NAMESPACE;
import hasthi.common.BaseContext;
import hasthi.common.ComponetFactory;
import hasthi.common.HasthiException;
import hasthi.common.ManagementNotifier;
import hasthi.common.Utils;
import hasthi.common.XmlBeanUtils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants;
import hasthi.common.constants.WSDMConstants.SystemState;
import hasthi.container.CommunicationContianier;
import hasthi.container.MessageContext;
import hasthi.container.MessageProcessor;
import hasthi.container.UnicastContianier;
import hasthi.container.XsulBasedWSDMProcessor;
import hasthi.manager.ExecuteCommandDocument;
import hasthi.manager.ExecuteCommandResponseDocument;
import hasthi.manager.ExecuteCommandDocument.ExecuteCommand;
import hasthi.manager.ExecuteCommandResponseDocument.ExecuteCommandResponse;
import hasthi.metamodel.resource.ManagedResource;
import hasthi.tools.TomcatUtils;
import hasthi.wsdm.AbstractManagedSystemHandle;
import hasthi.wsdm.ManagementAgentContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlObject;

/**
 * Host agent has two main tasks, to monitor and act as the agent of a host, and
 * on request of managers to carry out remote management actions in the host the
 * agent resides. Therefore management actions do not depend on SSH or GRAM for
 * remote actions.
 * 
 * @author hperera
 * 
 */
public class HostAgent implements MessageProcessor {
    protected XsulBasedWSDMProcessor processor;
    protected String hostName;
    protected CommunicationContianier container;
    protected Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);
    protected final BaseContext baseContext;
    protected ManagementAgentContext agentContext;

    public HostAgent(BaseContext baseContext) {
        this.baseContext = baseContext;
        hostName = Utils.getLocalHost();
    }

    public void addContainer(CommunicationContianier container) throws HasthiException {
        this.container = (CommunicationContianier) container;
    }

    public XmlObject processMessage(MessageContext messageContext) throws HasthiException {
        XmlObject request = messageContext.getRequest();
        // TODO special treatment for manager starts + send a startup event
        if (request instanceof ExecuteCommandDocument) {
            ExecuteCommand command = ((ExecuteCommandDocument) request).getExecuteCommand();
            if(command.getCmd().equals(COMMAND_TOMCAT_START)){
                return TomcatUtils.startTomcat(command.getWorkdir(), this, baseContext);
            }else if(command.getCmd().equals(COMMAND_TOMCAT_STOP)){
                TomcatUtils.shutDownTomcat(command.getWorkdir(), baseContext);
                ExecuteCommandResponseDocument document 
                    = ExecuteCommandResponseDocument.Factory.newInstance();
                ExecuteCommandResponse executeCommandResponse = document
                        .addNewExecuteCommandResponse();
                executeCommandResponse.setSucess(true);
                return document;
            }else if(command.getCmd().startsWith(COMMAND_PROCESS_KILL)){
                //todo this work only with /u/drlead/bin/zap avialablble
                String stopCommand = System.getenv("HOME")+"/bin/zap";
                if(!new File(stopCommand).exists()){
                    throw new HasthiException("Stop command " + stopCommand + " not found", FaultCode.LocalError);
                }
                String processName = command.getCmd().replace(COMMAND_PROCESS_KILL, "");
                String verification = new StringBuffer().append("(?s)Killing the following processes:.*").append(processName)
                    .append("|").append("No processes found that match ").toString();
                String finalCommand = new StringBuffer().append(stopCommand)
                    .append(" ").append(processName).toString();
                ExecuteCommandResponseDocument responseDocument = runJob(finalCommand, 
                        new String[]{"USER="+System.getenv("USER")}, command.getWorkdir(), verification);
                String stdout = responseDocument.getExecuteCommandResponse().getStdouterr(); 
                if(stdout != null && !stdout.contains("Killing the following processes:")){
                    throw new HasthiException("Falied to kill the process " + processName + " stdout =["
                            + stdout + "]", FaultCode.LocalError);
                }
                return responseDocument;
            }else{
                ExecuteCommandResponseDocument responseDocument = runJob(command.getCmd(), command
                        .getEnvArray(), command.getWorkdir(), command.getVerification());
                log.info("Command " + command.getCmd() + " Suceeds");
                return responseDocument;
            }
        } else if (processor.isKnownMessage(messageContext)) {
            return processor.processeMessage(messageContext);
        } else {
            throw new HasthiException("Unsupported Message " + XmlBeanUtils.prettyPrint(request),
                    FaultCode.InvaliedRequest);
        }
    }

    public ExecuteCommandResponseDocument runJob(String command, String[] nnv, String workdir,
            String verification) throws HasthiException {
        try {
            // String realCommand = "nohup "+command;
            
            String fileName = command2Name(command);
            File scriptFile = File.createTempFile("hasthijob"+fileName, ".sh");
            File logfile = File.createTempFile("hasthijob"+fileName, ".log");

            FileWriter out = new FileWriter(scriptFile);
            // StringBuffer out = new StringBuffer();
            out.write("#!/bin/bash\n");
            out.write("cd "+workdir+"\n");
            out.write("source ~/.bashrc\n");
            if(nnv != null){
                for(String env:nnv){
                    out.write("export ");
                    out.write(env);
                    out.write("\n");
                }
            }
            out.write("nohup ");
            out.write(command);
            out.write(" ");
            out.write("1>" + logfile + " 2>" + logfile);
            out.write(" &\n");
            out.close();

            Process process = null;
            
            process = Runtime.getRuntime().exec("/bin/chmod 755 " + scriptFile.getAbsolutePath(),
                    nnv, new File(workdir));
            process.waitFor();
            
            process = Runtime.getRuntime().exec(scriptFile.getAbsolutePath(), nnv,
                    new File(workdir));
            log.info("Execuate Command " + scriptFile.getAbsolutePath() + " workdir =" + workdir);
            // Thread.sleep(1000);
            process.waitFor();
            String stdout = "";
            long start = System.currentTimeMillis();
            
            Pattern pattern = null;
            if(verification != null && verification.length() > 0){
                pattern = Pattern.compile(verification);
            }
            while (System.currentTimeMillis() - start < 2 * 60 * 1000) {
                if (logfile.exists()) {
                    stdout = Utils.readFile(logfile.getAbsolutePath());
                    String[] results;
                    Matcher matcher = null;
                    if(pattern == null || (matcher = pattern.matcher(stdout)).find()){
                        ExecuteCommandResponseDocument document 
                            = ExecuteCommandResponseDocument.Factory.newInstance();
                        ExecuteCommandResponse executeCommandResponse = document
                                .addNewExecuteCommandResponse();
                        executeCommandResponse.setSucess(true);
                        executeCommandResponse.setStdouterr(stdout);
                        
                        if(matcher != null){
                            results = new String[matcher.groupCount()];
                            for(int i = 0;i<results.length;i++){
                                results[i] = matcher.group(i);
                            }
                            executeCommandResponse.setResponseParamsArray(results);    
                        }
                        //We sucessful we delete the files, otherwise keep them
                        //scriptFile.delete();
                        //logfile.delete();
                        return document;
                    }
                }
                Thread.sleep(2 * 1000);
            }
            throw new HasthiException("Falied to run the command " + command + " stdout =["
                    + stdout + "] does not have string " + verification, FaultCode.LocalError);
        } catch (IOException e) {
            throw new HasthiException(e, FaultCode.InternalServiceError);
        } catch (InterruptedException e) {
            throw new HasthiException(e, FaultCode.InternalServiceError);
        }
    }

    public void initManagmentAgent() throws HasthiException {
        agentContext = new ManagementAgentContext(baseContext);
        processor = new XsulBasedWSDMProcessor(agentContext);
        processor.addResource(new HostSystemHandle(new QName(hostName)));
        log.info("Starting default host agent");
    }

    public class HostSystemHandle extends AbstractManagedSystemHandle {
        protected Map<QName, Object> systemProperties = new HashMap<QName, Object>();
        public HostSystemHandle(QName name) throws HasthiException {
            super(name, name.getLocalPart());
            supportedCapabilityNames.clear();
            supportedCapabilityNames.add(CAPABILITY_DESCRIPTION);
            supportedCapabilityNames.add(CAPABILITY_MATRICS1);
            supportedCapabilityNames.add(CAPABILITY_OPERATIONAL_STATUS);
            supportedCapabilityNames.add(CAPABILITY_OPERATIONAL_STATUS2);
            supportedCapabilityNames.add(CAPABILITY_STATE);
            supportedCapabilityNames.add(CAPABILITY_HEARTBEAT);
            supportedCapabilityNames.add(CAPABILITY_CONFIGURATION);
            supportedCapabilityNames.add(CAPABILITY_CATEGORY);
            
            systemProperties.put(new QName(MUWS2_NAMESPACE, "Caption"), hostName);
            systemProperties.put(new QName(MUWS2_NAMESPACE, "Description"), hostName);
            systemProperties.put(WSDMConstants.RESOURCE_CATEGORY_PARAM, ManagedResource.ResourceType.Host
                    .toString());
        }

        public Object getMatric(QName name) throws HasthiException {
            // TODO Auto-generated method stub
            return null;
        }

  

        public SystemState getCurrentSystemState() {
            // int pendingRequests = requestReceived - requestSucessful -
            // requestFalied;
            // if (pendingRequests > 10) {
            // return SystemState.SaturatedState;
            // } else if (pendingRequests > 0) {
            // return SystemState.BusyState;
            // } else {
            // return SystemState.IdleState;
            // }
            // TODO decide based on CPU usage
            return SystemState.IdleState;
        }

        public SystemState[] getLastStateTransition() {
            // int pendingRequests = requestReceived - requestSucessful -
            // requestFalied;
            // if (pendingRequests > 10) {
            // return new SystemState[] { SystemState.BusyState,
            // SystemState.SaturatedState };
            // } else if (pendingRequests > 0) {
            // return new SystemState[] { SystemState.IdleState,
            // SystemState.BusyState };
            // } else {
            // if (requestSucessful > 0) {
            // return new SystemState[] { SystemState.BusyState,
            // SystemState.IdleState };
            // } else {
            // return new SystemState[] { SystemState.StoppedState,
            // SystemState.IdleState };
            // }
            // }
            return new SystemState[] { SystemState.StoppedState, SystemState.IdleState };
        }

        public ManagementNotifier getNotifier() {
            return null;
        }

        public String getSystemEndPoint() {
            return container.getContainierAddress();
        }

        public void shutDown() throws HasthiException {
            container.stopContainer();
            // Do we need to run halt -p here, may be not
        }
        
        public void setConfiguration(QName name, Object value) throws HasthiException {
            if(systemProperties.containsKey(name) || supportedConfigurationNames.contains(name)){
                systemProperties.put(name, value);
            }else{
                throw new HasthiException("Does not support configuration " + name,
                        FaultCode.InvaliedLocalArgumnet);
            }
        }
        
        public Object getConfiguration(QName name) throws HasthiException {
            return systemProperties.get(name);
        }
    }

    public String startAgent() throws HasthiException {
        UnicastContianier container = ComponetFactory.createSoapContianier(baseContext, this,HOST_AGENT_PORT,true);
        container.startContainer();
        initManagmentAgent();
        baseContext.getDlogger().info("HostAgent " + container.getContainierAddress() + " Started");
        // TODO figure out is there a another agent running in the same machine
        // by reading a file ~/.hasthi/hostagent
        // for the url of the agent and try to connect to the agent. Only start
        // IFF no host agent is already running
        // String user = System.getProperty("user.home");
        return container.getContainierAddress();
    }

    public String getAgentUrl() {
        return container.getContainierAddress();
    }

    public static void main(String[] args)  {
        try{
            Class className = Class.forName("hasthi.lead.SigarBasedHostAgent");
            Method mainMethod = className.getMethod("main",String[].class);
            mainMethod.invoke(null, (Object)args);
        }catch(Exception e){
            System.out.println("Can not find SigarBasedHostAgent, Switching to default version" +e.getMessage());    
            if(!(e instanceof ClassNotFoundException)){
                e.printStackTrace();
            }
            try {
                BaseContext baseContext;
                if(args.length == 0){
                    baseContext =  new BaseContext();
                }else{
                    Properties properties = new Properties();
                    FileInputStream in = new FileInputStream(args[0]);
                    properties.load(in);
                    in.close();
                    baseContext = new BaseContext(properties);
                }
                
                HostAgent agent = new HostAgent(baseContext);
                agent.startAgent();
            } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
    }
    
    private String command2Name(String cmd){
        int index = cmd.lastIndexOf('/');
        if(index > 0 && index < cmd.length()-4){
            cmd = cmd.substring(index);
        }
        cmd = cmd.replaceAll("[^A-Za-z0-9_]", "");
        if(cmd.length() > 0){
            cmd = cmd.substring(0,Math.max(cmd.length(), 8));
        }
        return cmd;
    }
    
    
}
